/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ /*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * ns-2 simple.tcl script (ported from ns-2)
 * Originally authored by Steve McCanne, 12/19/1996
 */

// Port of ns-2/tcl/ex/simple.tcl to ns-3
//
// Network topology
//
//  h0                                 h0-n0, h1-n0, n1-h2, n2-h2: 2Mb/s, 10ms
//     \    ---- n1 -----              n0-n1, n0-n2:               2Mb/s, 50ms
//      \  /             |
//       n0              n3 --- h2
//      /  \             |
//     /    ---- n2 ----
//  h1
//
// - all links are point-to-point links with indicated one-way BW/delay
// - FTP/TCP flow from n0 to n3, starting at time 1.2 to time 1.35 sec.
// - DropTail queues 
// - Tracing of queues and packet receptions to file "simple-global-routing.tr"

#include <iostream>
#include <fstream>
#include <string>
#include <cassert>

#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>

#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
#include "reflex-trace.h"
#include "reflex-aux.h"
#include "ns3/config-store.h"



using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("SimpleGlobalRoutingExample");

void
LogTime ()
{
  NS_LOG_INFO ("Time is " << Simulator::Now());
  Simulator::Schedule(Seconds(5), MakeEvent( &LogTime));
}

 


double
FwndUpdate(std::vector<Ptr<Ipv4ReactTableEntry> > *availableRoutes, double sumLoss, double sumFwnd)
{
  //NS_LOG_INFO("sum loss " << sumLoss);
  int numPaths = availableRoutes->size();
  Ptr<Ipv4ReactTableEntry> route;
  std::vector<Ptr<Ipv4ReactTableEntry> >::iterator routeIter;

  //double m_fwndMin = 0.0;
  //if it was in the default table, must exist
  NS_ASSERT(numPaths);
  //not much choice here...
  if(numPaths == 1)
  {
    route = availableRoutes->front();
    route->SetFwnd(1);
    return (1.0);
  }

  //recalculate fwnd for entries
  double currentFwnd;
  double newSumFwnd = 0.0;
  //double loss;

  /*if (sumLoss) {

    for( routeIter = availableRoutes->begin(); routeIter != availableRoutes->end(); routeIter++ ) 
    {
      route = (*routeIter);
      currentFwnd = route->GetFwnd();
      loss = route->GetLoss();
      double diff = ((1.0/numPaths) - (loss/sumLoss));
      //diff *=0.5;
      NS_LOG_INFO ("diff " << diff);
      currentFwnd = currentFwnd * (1.0 + diff) * (1.0 - m_fwndMin*numPaths) + m_fwndMin;
      route->SetFwnd(currentFwnd);
      newSumFwnd += currentFwnd;
    }

  } else {*/
    //aim for equal utilization in absence of loss
    currentFwnd = 1.0/numPaths;
    newSumFwnd = 1;
    for( routeIter = availableRoutes->begin(); routeIter != availableRoutes->end(); routeIter++ )
    {
      route = (*routeIter);
      route->SetFwnd(currentFwnd);
    }
  //}

  return newSumFwnd;
}


int
main (int argc, char *argv[])
{
   //NS_ASSERT (argc > 3);
   //std::string dirName = argv [1];
   //std::string prefix = "reflex66";
//   std::string dirName = prefix + "-test";// + argv[1] + "-" + argv[2] + "-" + argv[3];
//   Chdir (dirName);
   CommandLine cmd;
   cmd.Parse (argc,argv);
   
   ConfigStore config;
   config.ConfigureDefaults ();
       



  // Users may find it convenient to turn on explicit debugging
  // for selected modules; the below lines suggest how to do this
#if 1
  LogComponentEnable ("SimpleGlobalRoutingExample", LOG_LEVEL_INFO);
  //LogComponentEnable ("ReflexTraceQueue", LOG_LEVEL_INFO);
  //LogComponentEnable("GlobalRoutingHelper", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4GlobalRouting", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4ReactRouting", LOG_LEVEL_INFO);
  //LogComponentEnable("Ipv4ReactRoutingHelper", LOG_LEVEL_ALL);
  //LogComponentEnable("Ipv4ReactTableEntry", LOG_LEVEL_ALL);
  //LogComponentEnable("FlowApplication", LOG_LEVEL_ALL);
//  LogComponentEnable("NscTcpSocketImpl", LOG_LEVEL_ALL);
#endif
  Time endtime = Seconds(1000);
                                                       
  NS_LOG_INFO ("Create nodes.");
  NodeContainer hosts;
  hosts.Create (6);
  NodeContainer core;
  core.Create (6);
  NodeContainer ingress;
  ingress.Create (3);
  NodeContainer egress;
  egress.Create (3);
 
  //hosts to ingress nodes
  NodeContainer h0i0 = NodeContainer (hosts.Get(0), ingress.Get (0));
  NodeContainer h1i1 = NodeContainer (hosts.Get(1), ingress.Get (1));
  NodeContainer h2i2 = NodeContainer (hosts.Get(2), ingress.Get (2));

  //ingress nodes to core nodes
  NodeContainer i0c0 = NodeContainer (ingress.Get(0), core.Get (0));
  NodeContainer i0c1 = NodeContainer (ingress.Get(0), core.Get (1));
  NodeContainer i1c0 = NodeContainer (ingress.Get(1), core.Get (0));
  NodeContainer i1c2 = NodeContainer (ingress.Get(1), core.Get (2));
  NodeContainer i2c1 = NodeContainer (ingress.Get(2), core.Get (1));
  NodeContainer i2c2 = NodeContainer (ingress.Get(2), core.Get (2));
     
  //internal core connections
  NodeContainer c0c3 = NodeContainer (core.Get(0), core.Get (3));
  NodeContainer c1c4 = NodeContainer (core.Get(1), core.Get (4));
  NodeContainer c2c5 = NodeContainer (core.Get(2), core.Get (5));

  //core to egress
  NodeContainer c3e0 = NodeContainer (core.Get(3), egress.Get (0));
  NodeContainer c3e1 = NodeContainer (core.Get(3), egress.Get (1));
  NodeContainer c4e0 = NodeContainer (core.Get(4), egress.Get (0));
  NodeContainer c4e2 = NodeContainer (core.Get(4), egress.Get (2));
  NodeContainer c5e1 = NodeContainer (core.Get(5), egress.Get (1));
  NodeContainer c5e2 = NodeContainer (core.Get(5), egress.Get (2));

  //egress to hosts
  NodeContainer e0h3 = NodeContainer (egress.Get (0), hosts.Get(3));
  NodeContainer e1h4 = NodeContainer (egress.Get (1), hosts.Get(4));
  NodeContainer e2h5 = NodeContainer (egress.Get (2), hosts.Get(5));

  //install react routing in ingress nodes
  InternetStackHelper reactStack;
  Ipv4ReactRoutingHelper reactRoutingHelper;
  //reactRoutingHelper.SetAttribute("BalancingMode", IntegerValue(atoi(argv[1])));
  reactRoutingHelper.SetAttribute("FwndUpdateFunc", CallbackValue(MakeCallback(&FwndUpdate)));
  reactRoutingHelper.SetAttribute("UpdateFwndInterval", TimeValue(Seconds(60)));

  //Callback <double, std::vector<Ptr<Ipv4ReactTableEntry> >, double, double> fwndUpdateCB;
  //fwndUpdateCB = MakeCallback(&FwndUpdate);
  //MakeCallback(&FwndUpdate);
  Ipv4GlobalRoutingHelper ipv4GlobalRoutingHelper;
  Ipv4ListRoutingHelper listRouting;
  listRouting.Add (reactRoutingHelper, 1);
  listRouting.Add (ipv4GlobalRoutingHelper, -1);
  reactStack.SetRoutingHelper (listRouting);    
  reactStack.Install (ingress);

  //install default stack in core and egress
  InternetStackHelper internet;
  internet.Install (core);
  internet.Install (egress);

  //install NSC stack in hosts
  internet.SetTcp ("ns3::NscTcpL4Protocol","Library",StringValue("liblinux2.6.26.so"));
  internet.Install (hosts);
 
  //activate reflex on hosts.
  NodeContainer::Iterator node;
  NodeContainer reflex_hosts = hosts;
  for ( node = reflex_hosts.Begin(); node != reflex_hosts.End(); node++)
  {
      std::ostringstream tcp_ecn;

      tcp_ecn << "/NodeList/" << (*node)->GetId() << "/$ns3::Ns3NscStack<linux2.6.26>/net.ipv4.tcp_ecn";
      Config::Set (tcp_ecn.str(), StringValue("3"));
  }
    
  NS_LOG_INFO ("Create channels.");
  PointToPointHelper p2p;

  NS_LOG_INFO ("\thosts to ingress.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("10ms"));
  NetDevices nd_h_i;
  nd_h_i.push_back (p2p.Install (h0i0));
  nd_h_i.push_back (p2p.Install (h1i1));
  nd_h_i.push_back (p2p.Install (h2i2));

  NS_LOG_INFO ("\tingress to core.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("10ms"));
  NetDevices nd_i_c;
  nd_i_c.push_back (p2p.Install (i0c0));
  nd_i_c.push_back (p2p.Install (i0c1));
  nd_i_c.push_back (p2p.Install (i1c0));
  nd_i_c.push_back (p2p.Install (i1c2));
  nd_i_c.push_back (p2p.Install (i2c1));
  nd_i_c.push_back (p2p.Install (i2c2));

  NS_LOG_INFO ("\tcore to egress.");
  NetDevices nd_c_e;
  nd_c_e.push_back (p2p.Install (c3e0));
  nd_c_e.push_back (p2p.Install (c4e2));
  nd_c_e.push_back (p2p.Install (c5e1));
  nd_c_e.push_back (p2p.Install (c3e1));
  nd_c_e.push_back (p2p.Install (c4e0));
  nd_c_e.push_back (p2p.Install (c5e2));

  NS_LOG_INFO ("\tcore to core.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("10Mbps"));
  NetDevices nd_c_c;
  nd_c_c.push_back (p2p.Install (c0c3));
  p2p.SetDeviceAttribute ("DataRate", StringValue ("20Mbps"));
  nd_c_c.push_back (p2p.Install (c1c4));
  nd_c_c.push_back (p2p.Install (c2c5));

  //egress to host
  NS_LOG_INFO ("\tegress to host.");
  p2p.SetDeviceAttribute ("DataRate", StringValue ("100Mbps"));
  p2p.SetChannelAttribute ("Delay", StringValue ("10ms"));
  NetDevices nd_e_h;
  nd_e_h.push_back (p2p.Install (e0h3));
  nd_e_h.push_back (p2p.Install (e1h4));
  nd_e_h.push_back (p2p.Install (e2h5));
                      

  //nd_e_p.push_back(nd_e_h);
  
  // Later, we add IP addresses.  
  NS_LOG_INFO ("Assign IP Addresses.");
  Ipv4AddressHelper ipv4;
  Ipv4InterfaceContainer interfaces;

  NetDevicesIterator iter;

  ipv4.SetBase ("172.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_h_i);

  ipv4.SetBase ("10.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_i_c);
  assign_network (&ipv4, &nd_c_c);
  assign_network (&ipv4, &nd_c_e);
  ipv4.SetBase ("192.0.0.0", "255.255.255.0");
  assign_network (&ipv4, &nd_e_h);

  // Create router nodes, initialize routing database and set up the routing
  // tables in the nodes.
  ipv4GlobalRoutingHelper.PopulateRoutingTables ();
 
  config.ConfigureAttributes ();
  // Create react routes for each ingress to each destination
  NodeContainer sources = NodeContainer (hosts.Get(0), hosts.Get(1), hosts.Get(2));
  //NodeContainer sources = NodeContainer (hosts.Get(1));
  //NodeContainer destinations = NodeContainer (hosts.Get(4));
  NodeContainer destinations = NodeContainer (hosts.Get(3), hosts.Get(4), hosts.Get(5));
  int h_index=0;
  // install routing table for each ingress
  for (NodeContainer::Iterator iter = ingress.Begin(); iter != ingress.End(); iter++) {
    Ptr<Ipv4> ipv4 = (*iter)->GetObject<Ipv4>();
    NS_ASSERT(ipv4 != 0);
    //check for right topology
    NS_ASSERT(ipv4->GetNInterfaces()==4);

    Ptr<Ipv4ReactRouting> react_routing = reactRoutingHelper.GetReactRouting (ipv4);

    //set to know which path to use (a bit of a hack)
    int flag=0;
    int d_index=0;
    printIPs(destinations);
    for (NodeContainer::Iterator dst = destinations.Begin(); dst != destinations.End(); dst++) {
      Ptr<Ipv4> dst_ipv4 = (*dst)->GetObject<Ipv4>();
      NS_ASSERT(dst_ipv4 != 0);
      //only support single address for now, interface 1
      NS_ASSERT(dst_ipv4->GetNInterfaces() == 2);
      Ipv4InterfaceAddress ipv4Address = dst_ipv4->GetAddress(1,0);
      Ipv4Address network = ipv4Address.GetLocal().CombineMask(ipv4Address.GetMask());

      // only case in which two paths exist
      std::cout << h_index << " " << d_index << std::endl;
      if (h_index == d_index) {
        react_routing->AddDefaultNetworkRouteTo (network, ipv4Address.GetMask(), 2);
        react_routing->AddNetworkRouteTo (network, ipv4Address.GetMask(), 3);
        std::cout << "Added " << network << " to interface 2\n";
        std::cout << "Added " << network << " to interface 3\n";
      } else if (!flag){
        react_routing->AddDefaultNetworkRouteTo (network, ipv4Address.GetMask(), 2);
        std::cout << "Added " << network << " to interface 2\n";
        flag = 1;
      } else {
        react_routing->AddDefaultNetworkRouteTo (network, ipv4Address.GetMask(), 3);
        std::cout << "Added " << network << " to interface 3\n";
      }
      d_index++;
    }
    h_index++;
    flag = 0;
  }

  NS_LOG_INFO ("Create Applications.");
  NS_LOG_INFO ("Create sink.");

  uint16_t port = 8080;
  //int destNode = atoi(argv[3]);
  //std::cout << destNode << '\n';
  NodeContainer appDestinations = NodeContainer (hosts.Get(atoi(argv[2])));
  //NodeContainer appDestinations = destinations;
  Time starttime = Seconds(1.0);
  for (NodeContainer::Iterator iter = appDestinations.Begin(); iter != appDestinations.End(); iter++) {
    Ptr<Ipv4> dst_ipv4 = (*iter)->GetObject<Ipv4>();
    Ipv4InterfaceAddress ipv4Address = dst_ipv4->GetAddress(1,0);

    // install sink in each destination
    FlowAppHelper sink (Address (InetSocketAddress (ipv4Address.GetLocal(), port)));
    sink.SetAttribute ("FlowNum", UintegerValue (0));
    sink.SetAttribute ("Local", AddressValue (InetSocketAddress (Ipv4Address::GetAny (), port)));
    ApplicationContainer appsink = sink.Install (*iter);
    appsink.Start (Seconds(0.0));
    appsink.Stop (endtime);

    //install sources
    FlowAppHelper onoff (Address (InetSocketAddress (ipv4Address.GetLocal(), port)));
    onoff.SetAttribute ("FlowNum", UintegerValue (50));
    //onoff.SetAttribute ("FlowSize", RandomVariableValue (ConstantVariable (1000000)));
    onoff.SetAttribute ("FlowSize", RandomVariableValue (WeibullVariable (2*1000*1000, 0.5)));
    //ApplicationContainer appsource = onoff.Install (sources);
    int srcNode = atoi(argv[1]);
    ApplicationContainer appsource = onoff.Install (hosts.Get(srcNode));
    //ApplicationContainer appsource = onoff.Install (sources);
    appsource.Start (starttime);
    appsource.Stop (endtime);
  
  }

  NS_LOG_INFO ("Run Simulation.");
  LogTime();
  //Ptr<Node> balancer = ingress.Get (0);
  //Ptr<Ipv4> ipv4_balancer = balancer->GetObject<Ipv4> ();
  //Simulator::Schedule (Seconds (10),&Ipv4::SetDown,ipv4_balancer, 2);
  //Simulator::Schedule (Seconds (10), &Ipv4GlobalRoutingHelper::RecomputeRoutingTables);
  //Simulator::Schedule (Seconds (20),&Ipv4::SetUp, ipv4_balancer, 2);

  //Ptr<Ipv4ReactRouting> routeInstance = reactRoutingHelper.GetReactRouting(ipv4_balancer);
  //routeInstance->AddNetworkRouteTo ("192.168.0.0", "255.255.255.0", 3);

  reactRoutingHelper.EnableAscii("reflex",ingress);
  reactRoutingHelper.TraceQueue("reflex", core);

  Simulator::Stop (endtime);
  Simulator::Run ();
  NS_LOG_INFO ("Done.");

  Simulator::Destroy ();
  return 0;
}
